Skip to content

fix(query): optimize numeric if branch selection - #20232

Draft
dantengsky wants to merge 2 commits into
databendlabs:mainfrom
dantengsky:fix/numeric-if-branch-selection
Draft

fix(query): optimize numeric if branch selection#20232
dantengsky wants to merge 2 commits into
databendlabs:mainfrom
dantengsky:fix/numeric-if-branch-selection

Conversation

@dantengsky

@dantengsky dantengsky commented Jul 30, 2026

Copy link
Copy Markdown
Member

I hereby agree to the terms of the CLA available at: https://docs.databend.com/dev/policies/cla/

Summary

Optimize final row selection for a binary if when the result is numeric or decimal.

The generic evaluator selects every row through type-erased scalar values and a generic column builder. This is expensive after the condition and both result branches have already been evaluated.

Implementation

  • Add a typed selector for a single-condition binary if
  • Support numeric and decimal outputs, including nullable variants
  • Handle scalar/scalar, scalar/column, column/scalar, and column/column results
  • Keep the existing generic selector for multi-branch and unsupported result types
  • Add golden tests for non-nullable Int64 and Decimal64 results

The typed path is used only when the block has a known row count, the if has exactly one condition, and the result type is supported.

Performance

This is an evaluator benchmark, not an end-to-end databend-query SQL benchmark.

The benchmark uses the production release profile, 30 samples per case, and two block sizes:

  • 65,536 rows: the default max_block_size
  • 1,048,576 rows: a larger stress case

Expression parsing, type checking, and input construction are outside the timed section. Evaluator::run and output allocation are timed. Divan drops the returned output after the timed section.

Both revisions were built as separate release binaries. The binaries were pinned to CPU 1 and run five times in balanced interleaved order on an otherwise idle 16-CPU host. The tables report the median of the five Divan medians.

Numeric results

Case Rows Before After Time reduction Speedup
if_contains 65,536 3.631 ms 1.294 ms 64.4% 2.81x
if_modulo 65,536 3.787 ms 1.508 ms 60.2% 2.51x
if_contains 1,048,576 64.78 ms 27.21 ms 58.0% 2.38x
if_modulo 1,048,576 67.47 ms 30.96 ms 54.1% 2.18x

The numeric reduction is large and directionally consistent at both block sizes.

Unsupported Boolean fallback

if_boolean returns Boolean, so it does not use the new typed selector and continues through the existing generic selector.

Rows Before After Observed change
65,536 3.862 ms 3.955 ms 2.4% slower
1,048,576 62.07 ms 63.47 ms 2.3% slower

The after revision is consistently slower for this case in this evaluator microbenchmark. The added fallback work is one output-type dispatch per block, while the measured gap grows with the number of rows, so that dispatch cannot explain the result. Treat this as an unresolved regression: the current benchmark times condition evaluation, both result branches, and final selection together, and does not yet identify which stage regressed.

Absolute times depend on the machine. Run the comparison locally and focus on matching cases and row counts.

Reproduce

The first commit contains only the benchmark and measures the old evaluator. The second commit adds the optimization without changing the benchmark.

On Linux, build first and then pin the cached benchmark run to one CPU:

git switch --detach 365900f8b7

env CARGO_TARGET_DIR=target/numeric-if-before \
  cargo bench --profile release \
  -p databend-common-functions --bench bench --no-run

taskset -c 1 env CARGO_TARGET_DIR=target/numeric-if-before \
  cargo bench --profile release \
  -p databend-common-functions --bench bench -- numeric_if

git switch --detach ad085925ef

env CARGO_TARGET_DIR=target/numeric-if-after \
  cargo bench --profile release \
  -p databend-common-functions --bench bench --no-run

taskset -c 1 env CARGO_TARGET_DIR=target/numeric-if-after \
  cargo bench --profile release \
  -p databend-common-functions --bench bench -- numeric_if

On systems without taskset, omit that prefix. Run on an idle machine and compare matching cases and row counts. For lower noise, repeat before and after in alternating order and compare medians. The output also reports rows per second.

--profile release is intentional: the repository bench profile enables debug assertions, while the production release profile does not. Separate target directories prevent Cargo artifacts from being reused across revisions.

Tests

  • Unit Test
  • Logic Test
  • Benchmark Test
  • No Test - Explain why

Commands run:

cargo fmt --all -- --check
cargo test -p databend-common-functions --test it scalars::control::test_control
cargo clippy -p databend-common-expression -p databend-common-functions --all-targets -- -D warnings
cargo bench --profile release -p databend-common-functions --bench bench -- numeric_if

Type of change

  • Bug Fix (non-breaking change which fixes an issue)
  • New Feature (non-breaking change which adds functionality)
  • Breaking Change (fix or feature that could cause existing functionality not to work as expected)
  • Documentation Update
  • Refactoring
  • Performance Improvement
  • Other (please describe):

AI assistance

  • AI usage: An AI coding agent verified the port onto current main, separated the benchmark baseline from the implementation, audited the benchmark timing and release profile, added reproducible block-size benchmarks, ran correctness, lint, and performance validation, and drafted this PR description
  • Responsible human: @dantengsky
  • The responsible human has read every line of this diff and can explain each change

This change is Reviewable

@github-actions github-actions Bot added the pr-bugfix this PR patches a bug in codebase label Jul 30, 2026
@dantengsky
dantengsky force-pushed the fix/numeric-if-branch-selection branch from 1d19bd9 to 2417c42 Compare July 30, 2026 10:25
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🤖 CI Job Analysis (Retry 1)

Workflow: 30540682108

📊 Summary

  • Total Jobs: 90
  • Failed Jobs: 1
  • Retryable: 0
  • Code Issues: 1

NO RETRY NEEDED

All failures appear to be code/test issues requiring manual fixes.

🔍 Job Details

  • linux / test_private_tasks: Not retryable (Code/Test)

🤖 About

Automated analysis using job annotations to distinguish infrastructure issues (auto-retried) from code/test issues (manual fixes needed).

@dantengsky
dantengsky marked this pull request as draft July 30, 2026 10:37
Extracted from commit 87eb181 so the pre-optimization baseline can be benchmarked directly.
Extracted from commit 87eb181 after separating the reproducible benchmark baseline.
@dantengsky
dantengsky force-pushed the fix/numeric-if-branch-selection branch from 2417c42 to ad08592 Compare July 30, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-bugfix this PR patches a bug in codebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant